package com.ccp.dev.workflow.listener;

import com.ccp.dev.core.basic.util.ContextUtil;
import com.ccp.dev.core.basic.util.UUIDUtils;
import com.ccp.dev.core.util.AppUtil;
import com.ccp.dev.core.util.BeanUtils;
import com.ccp.dev.core.util.StringUtil;
import com.ccp.dev.system.consts.SecretUtil;
import com.ccp.dev.system.dao.SysUserDao;
import com.ccp.dev.system.model.SysUser;
import com.ccp.dev.workflow.bpmconst.BpmConst;
import com.ccp.dev.workflow.model.*;
import com.ccp.dev.workflow.model.bpm.ProcessCmd;
import com.ccp.dev.workflow.model.bpm.TaskExecutor;
import com.ccp.dev.workflow.service.*;
import com.ccp.dev.workflow.service.bpm.BpmNodeUserUtil;
import com.ccp.dev.workflow.service.bpm.thread.MessageUtil;
import com.ccp.dev.workflow.service.bpm.thread.TaskThreadService;
import com.ccp.dev.workflow.service.bpm.thread.TaskUserAssignService;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.flowable.bpmn.model.Activity;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.service.delegate.DelegateTask;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;

/**
 * ClassDescribe:
 * 任务创建监听器
 * @author :wangcheng
 * Date: 2019-05-13
 */
@Component
public class TaskCreateListener extends BaseTaskListener {

    @Resource
    private TaskOpinionService taskOpinionService;
    @Resource
    private TaskUserAssignService taskUserAssignService;
    @Resource
    private TaskForkService taskForkService;
    @Resource
    private BpmNodeSetService bpmNodeSetService;
    @Resource
    private BpmService bpmService;
    @Resource
    private BpmProStatusService bpmProStatusService;
    @Resource
    private AgentSettingService agentSettingService;
    @Resource
    private SysUserDao sysUserDao;
    @Resource
    private ProcessRunService processRunService;
    @Resource
    private RuntimeService runtimeService;

    @Override
    protected void execute(DelegateTask delegateTask, String procDefId, String nodeId) {
        //设置任务状态。
        delegateTask.setDescription(TaskOpinion.STATUS_CHECKING.toString());

        //携带上一任务的分发令牌
        String token=TaskThreadService.getToken();
        if(token!=null){
            delegateTask.setVariableLocal(TaskFork.TAKEN_VAR_NAME, token);
        }
        //记录新产生的任务，为后续的回退作准备
        TaskThreadService.addTask((TaskEntity)delegateTask);
        //生成任务审批意见
        addOpinion(token,delegateTask);

        String procInstanceId=delegateTask.getProcessInstanceId();

        //启动流程时添加或保存状态数据。
        bpmProStatusService.addOrUpd(procDefId, procInstanceId,nodeId);

        Map<String,List<TaskExecutor>> nodeUserMap=taskUserAssignService.getNodeUserMap();
        //处理任务分发。
        boolean isHandForkTask=handlerForkTask(procDefId,nodeId,nodeUserMap,delegateTask);
        if(isHandForkTask){
            return;
        }

        boolean isSubProcess=handSubProcessUser(delegateTask);
        if(isSubProcess){
            return;
        }
        //处理多实例外部子流程用户。
        boolean isHandExtUser= handExtSubProcessUser(delegateTask);
        if(isHandExtUser){
            return;
        }

        //在启动时指定了执行人
        Object var = runtimeService.getVariable(delegateTask.getProcessInstanceId(),BpmConst.NODE_USER_ASSIGNEE);
        String nodeUserJson = "";
        if(null!=var){
            nodeUserJson = String.valueOf(var);
        }
        if (StringUtil.isNotEmpty(nodeUserJson)) {
            JSONArray jsonArray = JSONArray.fromObject(String.valueOf(nodeUserJson));
            for (Object obj : jsonArray) {
                JSONObject object = (JSONObject) obj;
                String setNodeId = object.getString("nodeId");
                String setUserId = object.getString("userId");
                String setUserName = object.getString("userName");
                if (nodeId.equals(setNodeId)) {
                    List<TaskExecutor> executorIds= new ArrayList<>(1);
                    executorIds.add(TaskExecutor.getTaskUser(setUserId,setUserName));
                    assignUser(delegateTask,executorIds);
                    return;
                }
            }
        }

        //在上一步中指定了该任务的执行人员
        if(nodeUserMap!=null && nodeUserMap.get(nodeId)!=null){
            List<TaskExecutor> executorIds=nodeUserMap.get(nodeId);
            assignUser(delegateTask,executorIds);
            return;
        }

        List<TaskExecutor> executorUsers = taskUserAssignService.getExecutors();
        //当前执行人。
        if(BeanUtils.isNotEmpty(executorUsers)){
            assignUser(delegateTask,executorUsers);
            return;
        }
        //处理从数据库加载用户，并进行分配。
        handAssignUserFromDb(procDefId,nodeId,delegateTask);

    }

    /**
     * 外部子流程流程多实例任务人员分配。
     * @param delegateTask
     * @return
     */
    private boolean handExtSubProcessUser(DelegateTask delegateTask) {
        //获取表单密级
        int dataPrivacyLevel = getDataPrivacyLevel(delegateTask);
        String executionId = delegateTask.getProcessInstanceId();
        // 流程刚刚开始，还没有保存到表数据
        ExecutionEntity execution = CommandContextUtil.getExecutionEntityManager().findById(executionId);
        if (execution == null) {
            return false;
        }
        // 没有父流程
        String procDefId = delegateTask.getProcessDefinitionId();
        String nodeId = delegateTask.getTaskDefinitionKey();
        FlowElement flowElement = bpmService.getFlowElement(procDefId, nodeId);
        ExecutionEntity superExecution = execution.getSuperExecution();
        if (superExecution == null) {
            return false;
        }
        // 不是多实例
        if (((Activity) flowElement).getLoopCharacteristics() == null) {
            return false;
        }

        Map<String, FlowNodeInfo> mapParent = NodeCache.getByProcDefId(procDefId);

        String parentNodeId = superExecution.getActivityId();
        String currentNodeId = execution.getActivityId();
        FlowNodeInfo parentNode = mapParent.get(parentNodeId);
        Map<String, FlowNodeInfo> subProcessNodes = parentNode.getSubProcessNodes();
        FlowNodeInfo startNode = NodeCache.getStartNode(subProcessNodes);

        if (startNode.getNextFlowNodesInfo().size() == 1) {
            FlowNodeInfo nextNode = startNode.getNextFlowNodesInfo().get(0);
            if (nextNode.getNodeId().equals(currentNodeId)) {
                TaskExecutor taskExecutor = (TaskExecutor) runtimeService.getVariable(superExecution.getId(), "assignee");
                if (taskExecutor != null) {
                    Map<String,Object> result = assignUser(delegateTask, taskExecutor,dataPrivacyLevel);
                    if((int)result.get(RESULT_NAME)==0){
                        //由于密级不足导致没有执行人
                        String msg=result.get(RESULT_MSG_NAME).toString();
                        MessageUtil.addMsg(msg);
                        throw new RuntimeException(msg);
                    }
                }
                return true;
            }
            return false;
        }

        return false;
    }

    /**
     * 处理内部子流程的人员分配。
     * <pre>
     * 	将任务的执行变量人取出，指定给任务执行人。
     * </pre>
     * @param delegateTask
     * @return
     */
    private boolean handSubProcessUser(DelegateTask delegateTask) {
        FlowNodeInfo flowNodeInfo = NodeCache.getByProcDefId(delegateTask.getProcessDefinitionId()).get(delegateTask.getTaskDefinitionKey());
        int dataPrivacyLevel = getDataPrivacyLevel(delegateTask);
        Boolean isMultipleNode = flowNodeInfo.getIsMultiInstance();
        if (!isMultipleNode) {
            return false;
        }

        // 若为多实例子流程中的任务，则从线程中的人员取出并把该人员从线程中删除
        TaskExecutor taskExecutor = (TaskExecutor) delegateTask.getVariable("assignee");
        if (taskExecutor != null) {
            // 分配任务执行人
            Map<String,Object> result  = assignUser(delegateTask, taskExecutor,dataPrivacyLevel);
            if((int)result.get(RESULT_NAME)==0){
                //由于密级不足导致没有执行人
                String msg=result.get(RESULT_MSG_NAME).toString();
                MessageUtil.addMsg(msg);
                throw new RuntimeException(msg);
            }

            int completeInstance=(Integer)delegateTask.getVariable("nrOfCompletedInstances");
            int nrOfInstances=(Integer)delegateTask.getVariable("nrOfInstances");
            //清空该人员集合
            if( completeInstance==nrOfInstances){
                delegateTask.removeVariable(BpmConst.SUBPRO_MULTI_USERIDS);
            }
        }

        return false;
    }

    /**
     * 添加流程任务意见。
     * @param token
     * @param delegateTask
     */
    private void addOpinion(String token,DelegateTask delegateTask){
        TaskOpinion taskOpinion=new TaskOpinion(delegateTask);
        taskOpinion.setOpinionId(UUIDUtils.getUUIDFor32());
        taskOpinion.setTaskToken(token);
        taskOpinionService.add(taskOpinion);
    }


    /**
     * 处理任务分发。
     * <pre>
     * 	1.根据指定的用户产生新的任务，并指定了相应的excution，任务历史数据。
     * 		支持用户独立的往下执行，不像会签的方式需要等待其他的任务完成才往下执行。
     *  2.产生分发记录。
     *
     * </pre>
     * @param procDefId			流程定义ID
     * @param nodeId			流程节点ID
     * @param nodeUserMap		上下文指定的分发用户。
     * @param delegateTask		任务对象。
     * @return
     */
    private boolean handlerForkTask(String procDefId,String nodeId,Map<String,List<TaskExecutor>> nodeUserMap,DelegateTask delegateTask){
        //若任务进行回退至分发任务节点上，则不再进行任务分发
        ProcessCmd processCmd=TaskThreadService.getProcessCmd();
        if(processCmd!=null && BpmConst.TASK_BACK.equals(processCmd.isBack())){
            return false;
        }
        BpmNodeSet bpmNodeSet=bpmNodeSetService.getByProcDefIdNodeIdOrParentProcDefId(nodeId,procDefId,null);
        //当前任务为分发任务,即根据当前分发要求生成分发任务
        if(bpmNodeSet!=null && BpmNodeSet.NODE_TYPE_FORK.equals(bpmNodeSet.getNodeType())){
            List<TaskExecutor> taskExecutors=taskUserAssignService.getExecutors();
            //若当前的线程里包含了该任务对应的执行人员列表，则任务的分发用户来自于此
            if(BeanUtils.isEmpty(taskExecutors)){
                //若当前的线程里包含了该任务对应的执行人员列表，则任务的分发用户来自于此
                if(nodeUserMap!=null && nodeUserMap.get(nodeId)!=null){
                    taskExecutors=nodeUserMap.get(nodeId);
                }
                //否则，从数据库获取人员设置
                else{
                    BpmNodeUserService userService=(BpmNodeUserService) AppUtil.getBean(BpmNodeUserService.class);
                    ProcessInstance processInstance=bpmService.getProcessInstance(delegateTask.getProcessInstanceId());
                    if(processInstance!=null){
                        Map<String,Object> vars=delegateTask.getVariables();
                        vars.put("executionId", delegateTask.getExecutionId());
                        String preTaskUser=ContextUtil.getCurrentUserId().toString();
                        String procInstId=delegateTask.getProcessInstanceId();
                        String startUserId=(String)delegateTask.getVariable(BpmConst.StartUser);
                        taskExecutors = userService.getExeUserIds(procDefId, procInstId, nodeId, startUserId, preTaskUser, vars);
                    }
                }
            }
            if(BeanUtils.isNotEmpty(taskExecutors)){
                bpmService.newForkTasks((TaskEntity)delegateTask, taskExecutors);
                taskForkService.newTaskFork(delegateTask,bpmNodeSet.getJoinTaskName(), bpmNodeSet.getJoinTaskKey(), taskExecutors.size());
            }
            else{
                ProcessRun processRun= processRunService.getByProcInstanceId(delegateTask.getProcessInstanceId());
                String msg=processRun.getSubject() + "请设置分发人员";
                MessageUtil.addMsg(msg);
                throw new RuntimeException(msg);

            }

            return true;
        }
        return false;
    }

    /**
     * 分配任务执行人。
     * @param delegateTask
     * @param executors
     */
    private void assignUser(DelegateTask delegateTask, List<TaskExecutor> executors){
        //获取表单密级
        String instanceId = delegateTask.getProcessInstanceId();
        int dataPrivacyLevel = getDataPrivacyLevel(delegateTask);
        if(BeanUtils.isEmpty(executors)){
            String msg="节点:" + delegateTask.getName() +",没有设置执行人";
            MessageUtil.addMsg(msg);
            throw new RuntimeException(msg);
        }

        //只有一个人的情况。
        if(executors.size()==1){
            TaskExecutor taskExecutor=executors.get(0);
            Map<String,Object> result = assignUser(delegateTask, taskExecutor,dataPrivacyLevel);
            if((int)result.get(RESULT_NAME)==0){
                //由于密级不足导致没有执行人
                String msg=result.get(RESULT_MSG_NAME).toString();
                MessageUtil.addMsg(msg);
                throw new RuntimeException(msg);
            }
        }
        else{
            List<TaskExecutor> executorList=getByTaskExecutors(executors);
            if(BeanUtils.isEmpty(executorList)){
                String msg="没有设置人员,请检查人员配置!";
                MessageUtil.addMsg(msg);
                throw new RuntimeException(msg);
            }
            //由于密级限制，应该把所有的用户都抽取出来进行筛选
            Set<String> userSet = new LinkedHashSet<>(16);
            setCandidateUsers(userSet,executorList,dataPrivacyLevel,delegateTask);
            if(userSet.size()<=0){
                String msg="设置的人员密级不够!";
                MessageUtil.addMsg(msg);
                throw new RuntimeException(msg);
            }
        }
    }

    /**
     * 设置任务候选人
     * @param userSet 若该集合有记录，则证明有候选人
     * @param executorList 需要过滤的执行人集合
     * @param dataPrivacyLevel 表单数据密级
     * @param delegateTask 任务
     */
    private void setCandidateUsers(Set<String> userSet,List<TaskExecutor> executorList,int dataPrivacyLevel,DelegateTask delegateTask){
        for (TaskExecutor ex : executorList) {
            if (ex.getType().equals(TaskExecutor.USER_TYPE_USER)) {
                //用户的情况
                SysUser tempUser = sysUserDao.getById(ex.getExecuteId());
                if(tempUser.getPrivacyLevel()>=dataPrivacyLevel){
                    delegateTask.addCandidateUser(tempUser.getUserId());
                    userSet.add(tempUser.getUserId());
                }
            } else {
                List<SysUser> userList=BpmNodeUserUtil.getUserListByExecutor(ex);
                for(SysUser sysUser:userList){
                    if(SecretUtil.getUserDataSecretMap().get(sysUser.getPrivacyLevel())>=dataPrivacyLevel){
                        delegateTask.addCandidateUser(sysUser.getUserId());
                        userSet.add(sysUser.getUserId());
                    }
                }
            }
        }
    }

    /**
     * 分配用户执行人或候选人
     * @param delegateTask
     * @param taskExecutor
     */
    private Map<String,Object> assignUser(DelegateTask delegateTask, TaskExecutor taskExecutor , int dataPrivacyLevel){
        Map<String,Object> result = new HashMap<>(2);
        //代理授权设置默认是true
        boolean isAgentSet = true;

        if(TaskExecutor.USER_TYPE_USER.equals(taskExecutor.getType())){
            String sysUserId = taskExecutor.getExecuteId();
            SysUser ownerUser = sysUserDao.getById(sysUserId);
            if(ownerUser.getPrivacyLevel()<dataPrivacyLevel){
                //代理人员的密级小于数据密级
                result.put(RESULT_NAME,0);
                result.put(RESULT_MSG_NAME,"下一节点审批人的密级不够！");
                return result;
            }
            SysUser sysUser =null;
            //取代理用户
            isAgentSet = agentSetOnlyOne(sysUserId,dataPrivacyLevel,delegateTask,taskExecutor,true);
            //授权与代理人员的密级判断
            if(!isAgentSet) {
                //代理人员的密级小于数据密级
                result.put(RESULT_NAME, 0);
                result.put(RESULT_MSG_NAME, "代理人员的密级不够！");
                return result;
            }
        }
        else{
            //由于密级限制，应该把所有的用户都抽取出来进行筛选
            Set<String> userSet = new LinkedHashSet<>();
            //多个人的情况
            List<TaskExecutor> list=getByTaskExecutor(taskExecutor);
            if(BeanUtils.isEmpty(list)){
                String msg=getNotAssignMessage(taskExecutor);
                MessageUtil.addMsg(msg);
            }
            setCandidateUsers(userSet,list,dataPrivacyLevel,delegateTask);
            //是否任务执行人仅为一人,是否开启授权代理
            if(userSet.size()==1&&isAllowAgent()){
                isAgentSet = agentSetOnlyOne(userSet.iterator().next(),dataPrivacyLevel,delegateTask,taskExecutor,false);
            }
            if(userSet.size()<=0 || !isAgentSet){
                //代理人员的密级小于数据密级
                result.put(RESULT_NAME,0);
                result.put(RESULT_MSG_NAME,"下一节点审批人的密级不够！");
                return result;
            }
        }
        result.put(RESULT_NAME,1);
        return result;
    }

    /**
     * 分配执行人（附带授权代理）
     * 2020-06-09 整合代码
     * @param sysUserId         授权用户ID
     * @param dataPrivacyLevel  授权用户密级
     * @param delegateTask      任务
     * @param isUserOnlyOne     是否为用户类型单人审核
     * @return
     */
    private boolean agentSetOnlyOne(String sysUserId, int dataPrivacyLevel, DelegateTask delegateTask, TaskExecutor taskExecutor, boolean isUserOnlyOne) {
        //是否执行代理
        SysUser agentUser = null;
        if(isAllowAgent()) {
            //获取代理人
            agentUser = agentSettingService.getAgent(delegateTask, sysUserId);
        }
        //代理授权判断
        if (BeanUtils.isNotEmpty(agentUser)) {
            if(agentUser.getPrivacyLevel()<dataPrivacyLevel){
                return false;
            }
            //设置代理人
            delegateTask.setAssignee(agentUser.getUserId());
            //更改任务状态为代理
            delegateTask.setDescription(TaskOpinion.STATUS_AGENT.toString());
        }else{
            if(!isUserOnlyOne){
                return true;
            }
            //2019-11-04 jyq修改
            //设置代理人
            delegateTask.setAssignee(taskExecutor.getExecuteId());
        }
        delegateTask.setOwner(sysUserId);
        //添加审批意见
        TaskOpinion taskOpinion= taskOpinionService.getByTaskId(delegateTask.getId());
        SysUser sysUser = sysUserDao.getById(sysUserId);
        taskOpinion.setExeUserId(sysUser.getUserId());
        taskOpinion.setExeFullname(sysUser.getFullName());
        taskOpinionService.update(taskOpinion);
        return true;
    }

    /**
     * 从数据库加载人员并分配用户。
     * @param actDefId
     * @param nodeId
     * @param delegateTask
     */
    private void handAssignUserFromDb(String actDefId,String nodeId,DelegateTask delegateTask){
        BpmNodeUserService userService=(BpmNodeUserService) AppUtil.getBean(BpmNodeUserService.class);

        String actInstId=delegateTask.getProcessInstanceId();

        ProcessInstance processInstance=bpmService.getProcessInstance(actInstId);
        List<TaskExecutor> users=null;
        //获取流程变量。
        Map<String,Object> vars=delegateTask.getVariables();

        vars.put(BpmConst.EXECUTION_ID_, delegateTask.getExecutionId());
        //执行任务的情况
        if(processInstance!=null){
            //获取上个任务的执行人，这个执行人在上一个流程任务的完成事件中进行设置。
            //代码请参考TaskCompleteListener。
            String startUserId=(String)vars.get(BpmConst.StartUser);

            String preStepUserId=ContextUtil.getCurrentUserId();
            String preStepOrgId=ContextUtil.getCurrentOrgId();
            vars.put(BpmConst.PRE_ORG_ID, preStepOrgId);

            if(StringUtil.isEmpty(startUserId) && vars.containsKey(BpmConst.PROCESS_INNER_VARNAME)){
                Map<String,Object> localVars=(Map<String,Object>)vars.get(BpmConst.PROCESS_INNER_VARNAME);
                startUserId=(String)localVars.get(BpmConst.StartUser);
            }

            users=userService.getExeUserIds(actDefId, actInstId, nodeId, startUserId,preStepUserId,vars);
        }
        //启动流程
        else{
            //startUser
            //上个节点的任务执行人
            String startUserId=(String)vars.get(BpmConst.StartUser);
            //内部子流程启动
            if(StringUtil.isEmpty(startUserId) && vars.containsKey(BpmConst.PROCESS_INNER_VARNAME)){
                Map<String,Object> localVars=(Map<String,Object>)vars.get(BpmConst.PROCESS_INNER_VARNAME);
                startUserId=(String)localVars.get(BpmConst.StartUser);
            }
            users=userService.getExeUserIds(actDefId, actInstId, nodeId, startUserId, startUserId,vars);
        }
        assignUser(delegateTask,users);
    }

    @Override
    protected int getScriptType() {
        return BpmConst.START_SCRIPT;
    }

    @Override
    protected int getBeforeScriptType() {
        return BpmConst.START_BEFORE_SCRIPT;
    }
}
